/* 
 * 
 * Copyright 2011 The University of Adelaide
 *
 *   Licensed under the Apache License, Version 2.0 (the "License");
 *   you may not use this file except in compliance with the License.
 *   You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License. 
 *  
 */

package au.org.aekos.ands.bdbsa.process;

import static au.org.aekos.ands.global.Util.earliest;
import static au.org.aekos.ands.global.Util.hasDescription;
import static au.org.aekos.ands.global.Util.hasName;
import static au.org.aekos.ands.global.Util.latest;
import static au.org.aekos.ands.global.Util.pluralise;

import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;

import org.apache.commons.lang3.StringUtils;

import au.org.aekos.ands.bdbsa.data.Flsp;
import au.org.aekos.ands.bdbsa.data.Luprojectbasis;
import au.org.aekos.ands.bdbsa.data.Supatch;
import au.org.aekos.ands.bdbsa.data.Superiod;
import au.org.aekos.ands.bdbsa.data.Superprojbasis;
import au.org.aekos.ands.bdbsa.data.Suspecies;
import au.org.aekos.ands.bdbsa.data.Susurvey;
import au.org.aekos.ands.bdbsa.data.Suvisit;
import au.org.aekos.ands.bdbsa.data.Vssp;
import au.org.aekos.ands.global.Schema;
import au.org.aekos.ands.global.Schema.Attribute;
import au.org.aekos.ands.global.Util;
import au.org.ands.standards.rifCs.registryObjects.ActivityDocument.Activity;
import au.org.ands.standards.rifCs.registryObjects.CollectionDocument.Collection;
import au.org.ands.standards.rifCs.registryObjects.CoverageType;
import au.org.ands.standards.rifCs.registryObjects.NameType;
import au.org.ands.standards.rifCs.registryObjects.NameType.NamePart;
import au.org.ands.standards.rifCs.registryObjects.RegistryObjectsDocument.RegistryObjects.RegistryObject;
import au.org.ands.standards.rifCs.registryObjects.SubjectType;

import com.google.common.base.Joiner;

/**
 * The collection generator for BDBSA.
 */
public class GenerateCollection {
	private Schema schema;
	private GenerateActivity activityGenerator;
	
	private static final String DEFAULT_PROJECTION = "GDA94";
	private static final String FALLBACK_NAME_PREFIX = "Biological Database of South Australia";

	/**
	 * Instantiates a new generate collection. Auxiliary and overlay
	 * metadata is provided by the metadata properties located on the classpath.
	 *
	 * @param schema the schema
	 */
	public GenerateCollection(Schema schema) {
		this.schema = schema;
		activityGenerator = new GenerateActivity(schema);
		
		initLookupMaps();
	}
	
	private Map<String,String> activityNameMap = new HashMap<String,String>();
	
	private static Map<Long,String> flspMap = new HashMap<Long,String>();
	private static Map<Long,String> vsspMap = new HashMap<Long,String>();
	private static boolean lookupMapsLoaded = false;
	
	/**
	 * Initialises the lookup maps for taxonomy information.
	 * Simple map from species number to name.
	 */
	private synchronized void initLookupMaps() {
		if (!lookupMapsLoaded) {	
			for (Flsp flsp : Flsp.findAllFlsps()) {
				flspMap.put(flsp.getSpeciesnr().longValue(), tweakTaxonomy(flsp.getGenus() + " " + flsp.getSp()));
			}
		
			for (Vssp vssp : Vssp.findAllVssps()) {
				vsspMap.put(vssp.getSpeciesnr().longValue(), tweakTaxonomy(vssp.getGenus() + " " + vssp.getSp()));
			}
			
			lookupMapsLoaded = true;
		}
	}

	// Generates the activity name corresponding to the given identifier.
	// Stores the activity name in the activity name map for future reference.
	// (Ratio of collections to activities is quite high.)

	private String getActivityName(String id) {
		String name = activityNameMap.get(id);
		if (name != null) { return name; }
		
	    Luprojectbasis basis = Luprojectbasis.findLuprojectbasis(id);
	    if (basis == null) { return null; }

	    Activity activity = activityGenerator.generateActivity(basis).getActivity();
	    for (NameType n : activity.getNameArray()) {
	    	if (n.getType().equalsIgnoreCase(Attribute.PRIMARY.toString())) {
	    		name = n.getNamePartArray(0).getStringValue();
	    	}
	    }
	    
	    if (name != null) { activityNameMap.put(id, name); }
	    
	    return name;
	}

	// Cosmetic name tweaking.

	private static String tweakTaxonomy(String s) {
		return StringUtils.capitalize(s.toLowerCase());
	}
	
	/**
	 * Generates a collection which is solely defined by metadata properties.
	 * No database is accessed for this generation procedure.
	 * (This is used for generation of the BDBSA supercollection.)
	 *
	 * @param id the identifier of the extra collection.
	 * @return a new registry object containing the extra collection.
	 */
	public RegistryObject generateExtraCollection(String id) {
		return schema.generateCollection(id);
	}

	/**
	 * Generates a collection for a given survey.
	 *
	 * @param survey the survey.
	 * @return a new registry object containing the collection.
	 */
	public RegistryObject generateCollection(Susurvey survey) {
		if (survey == null) { return null; }
		
		RegistryObject object = schema.generateCollection(survey.getSurveynr().toString());
		Collection collection = object.getCollection();
		
		// Name

		NameType[] names = collection.getNameArray();

		String longName = survey.getLusurveyname().getSurveynamefull();
		String shortName = survey.getLusurveyname().getSurveyname();
		
		if (!hasName(names, Attribute.PRIMARY)) {
			collection.addNewName().set(Schema.generateName(Attribute.PRIMARY, longName));
		}

		if (!hasName(names, Attribute.ABBREVIATED)) {
			// Only add abbreviated name if it is different in content to the primary name.
			if (!shortName.equalsIgnoreCase(longName)) {
				collection.addNewName().set(Schema.generateName(Attribute.ABBREVIATED, shortName));
			}
		}

		// Description

		if (!hasDescription(collection.getDescriptionArray(), Attribute.FULL)) {
			collection.addNewDescription().set(Schema.generateDescription(Attribute.FULL, survey.getStudyareadesc()));
		}

		// Coverage

		CoverageType coverage = collection.addNewCoverage();

		// Coverage (spatial and taxonomic)

		SortedSet<Double> latitudes = new TreeSet<Double>();
		SortedSet<Double> longitudes = new TreeSet<Double>();

		Set<Supatch> patches = survey.getSupatches();
		int patchCount = patches.size(), visitCount = 0;

		Set<String> taxonomy = new HashSet<String>();

		// For each patch, grab spatial information and visit set.
		// For each visit, fetch the species information.
		
		for (Supatch patch : patches) {
			if ((patch.getLatitude() != null) && (patch.getLongitude() != null)) {
				latitudes.add(patch.getLatitude().doubleValue());
				longitudes.add(patch.getLongitude().doubleValue());
			}
			
			Set<Suvisit> visits = patch.getSuvisits();
			visitCount += visits.size();

			for (Suvisit visit : visits) {
				Set<Suspecies> species = visit.getSuspecieses();
				for (Suspecies spec : species) {
					long speciesNumber = spec.getSpeciesnr().longValue();
					String speciesType = spec.getId().getSpeciestype();
						
					if (speciesType.equalsIgnoreCase("P")) {
						// Flsp flsp = Flsp.findFlsp(speciesNumber);
						// taxonomy.add(tweakTaxonomy(flsp.getGenus() + " " + flsp.getSp()));
						taxonomy.add(flspMap.get(speciesNumber));
					} else {
						// Vssp vssp = Vssp.findVssp(speciesNumber);
						// taxonomy.add(tweakTaxonomy(vssp.getGenus() + " " + vssp.getSp()));
						taxonomy.add(vsspMap.get(speciesNumber));
					}
				}
			}
		}
		
		// Add spatial coverage.
		
		if ((!latitudes.isEmpty()) && (!longitudes.isEmpty())) {
			double s = latitudes.first();
			double n = latitudes.last();
			double w = longitudes.first();
			double e = longitudes.last();
			coverage.addNewSpatial().set(Schema.generateSpatialCoverage(n, e, s, w, DEFAULT_PROJECTION));
		}

		// Add taxonomy subjects.
		
		if (!taxonomy.isEmpty()) {
			List<SubjectType> taxonList = Schema.generateSubjects("local", taxonomy);
			for (SubjectType s : taxonList) {
				collection.addNewSubject().set(s);
			}
		}

		// Coverage (temporal)

		Date minDate = null, maxDate = null;

		for (Superiod period : survey.getSuperiods()) {
			minDate = earliest(minDate, period.getStartdate());
			maxDate = latest(maxDate, period.getEnddate());
		}

		if ((minDate != null) || (maxDate != null)) {
			coverage.addNewTemporal().set(Schema.generateTemporalCoverage(minDate, maxDate));
		}

		// Link to activities and prefix collection name accordingly.

		Set<String> activityNames = new HashSet<String>();
		Set<String> activityIDs = new HashSet<String>();
		for (Superiod period : survey.getSuperiods()) {
			for (Superprojbasis basis : period.getSuperprojbases()) {
				Luprojectbasis luBasis = basis.getBasiscode();
				activityIDs.add(luBasis.getBasiscode());
				activityNames.add(getActivityName(luBasis.getBasiscode()));
			}
		}

		NameType primaryName = Util.getName(collection.getNameArray(), Attribute.PRIMARY);
		if (primaryName != null) {
			NamePart primaryNamePart = primaryName.getNamePartArray(0);
			String primaryNamePrefix = null;
			if (activityNames.isEmpty()) {
				primaryNamePrefix = FALLBACK_NAME_PREFIX;
			} else {
				primaryNamePrefix = Joiner.on(" / ").join(activityNames);
			}
			primaryNamePart.setStringValue(primaryNamePrefix + ": " + primaryNamePart.getStringValue());
		}

		for (String activityID : activityIDs) {
			collection.addNewRelatedObject().set(schema.linkToActivity(activityID));
		}

		// Notes

		if (patchCount > 0) {
			String patchString = pluralise(patchCount, "patch", true);
			String visitString = pluralise(visitCount, "visit", false);
			String noteString = "Collection includes " + patchString + " with " + visitString + ".";
			collection.addNewDescription().set(Schema.generateDescription(Attribute.NOTES, noteString));
		}
		
		return object;
	}
}
